import model.Block;
import model.Core;
import model.CorePlan;
import model.Host;
import model.HostPlan;
import model.InputModel;
import model.Job;
import model.JobPlan;
import model.OutputModel;
import model.Resource;
import util.AverageDivideUtil;
import util.FileUtil;
import util.JsonUtil;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author gubengang
 * @create 2021-09-30 19:07
 */
public class Solution {
    public void solve(String inputFile, String outputFile){
        String inputStr = FileUtil.readFile(inputFile);
        InputModel inputModel = JsonUtil.getObject(inputStr, InputModel.class);

        OutputModel outputModel = allocation(inputModel);
        String outStr = JsonUtil.getJSON(outputModel);
        FileUtil.writeFile(outputFile,outStr);
    }

    private OutputModel allocation(InputModel inputModel){
        Resource resource = new Resource();

        OutputModel outputModel = new OutputModel();
        for(Job job:inputModel.getBatchJobs()){
            outputModel.getBatchJobPlans().add(handleJob(job,resource));
        }

        return outputModel;
    }

    private JobPlan handleJob(Job job, Resource resource) {
        Map<Integer,List<Integer>> blockDispatchMap = new HashMap<>();
        for(Block block:job.getBlocks()){
           int hostId =  getHostId(block,resource);
           blockDispatchMap.computeIfAbsent(hostId,key->new ArrayList<>()).add(block.getBlockId());
        }

        Map<Integer,Block> blockMap = job.groupBlockIdMap();
        blockDispatchMap.forEach((hostId,blockIds)->{
            handleHost(job, resource.getHostByHostId(hostId),blockIds, blockMap);
        });

        JobPlan jobPlan = new JobPlan(job.getJobId());
        jobPlan.getHostPlans().addAll(buildHostPlans(job,resource));

        return jobPlan;
    }

    private List<HostPlan> buildHostPlans(Job job,Resource resource) {
        List<HostPlan> hostPlans = new ArrayList<>();
        for(Host host:resource.getHosts()){
            if(host.getJobCoreMap().containsKey(job.getJobId())){
                hostPlans.add(buildHostPlan(job,host));
            }
        }
        return hostPlans;
    }

    private HostPlan buildHostPlan(Job job, Host host) {
        HostPlan hostPlan = new HostPlan(host.getHostId());
        for(Core core: host.getCores()){
            if(core.getTaskMap().containsKey(job.getJobId())){
                CorePlan corePlan = new CorePlan();
                corePlan.getBlockIds().addAll(core.getTaskMap().get(job.getJobId()));
                hostPlan.getCorePlans().add(corePlan);
            }
        }
        return hostPlan;
    }


    private void handleHost(Job job, Host host, List<Integer> blockIds,Map<Integer,Block> blockMap) {
        List<Block> blocks = blockIds.stream()
                .map(blockMap::get)
                .collect(Collectors.toList());

        List<List<Block>> minGroup = new ArrayList<>();
        double minTime = Double.MAX_VALUE;
        int maxGroup = blocks.size();
        for(int i=1; i<=maxGroup;i++){
            List<List<Block>> blockGroups = AverageDivideUtil.average(blocks, i);
            double maxTime = AverageDivideUtil.costTime(blockGroups,job.getSpeedRate(i));
            if(maxTime < minTime){
                minTime = maxTime;
                minGroup = blockGroups;
            }
        }

        minGroup.forEach(k->handleCore(job, host, k));
    }

    private void handleCore(Job job, Host host, List<Block> blocks){
        Core selectedCore =null;
        for(Core core:host.getCores()){
            if(core.getTaskMap().get(job.getJobId())==null){
                selectedCore = core;
                break;
            }
        }

        if(selectedCore == null){
            selectedCore = host.getCores().get(0);
        }

        host.getJobCoreMap().computeIfAbsent(job.getJobId(),key->new ArrayList<>()).add(selectedCore.getCoreId());
        selectedCore.getTaskMap().computeIfAbsent(job.getJobId(),key->new ArrayList<>()).addAll(blocks.stream()
                .map(Block::getBlockId).collect(Collectors.toList()));
    }


    private int getHostId(Block block,Resource resource){
        List<Integer> hostIds = block.getHostIds();
        long costA = resource.getSizeByHostId(hostIds.get(0));
        long costB = resource.getSizeByHostId(hostIds.get(0));

        long blockSize = block.getSize()/(1024*1024);
        if(costA>costB){
            resource.addBlock2Host(hostIds.get(0),blockSize);
            return hostIds.get(1);
        }else{
            resource.addBlock2Host(hostIds.get(1),blockSize);
            return hostIds.get(0);
        }
    }
}
